﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Net;

using MMOEng.Packets;
using MMOEng.Packets.EnginePackets;

using RakNet;
using System.IO;
using MMOEng.Managers;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using System.Windows.Forms;
using System.Runtime.Serialization.Formatters.Binary;

namespace MMOEng
{
    public class NetworkManager : BaseManager
    {
        private RakPeerInterface interfaceRak;
        private Packet packet;
        private SystemAddress serverAddressRak;

        BitStream receiveBS;
        BitStream writeBS;

        string serverAddress;
        ushort serverPort;

        bool isConnected;

        public delegate void PacketReader(MMOGame game, byte[] data);

        private Dictionary<byte, PacketReader> packetCallbacks;

        public NetworkManager(MMOGame game, string address, ushort port) : base("NetworkManager",game)
        {
            if (!File.Exists("RakNet.dll"))
            {
                System.Console.WriteLine("Missing RakNet library file!");
                return;
            }

            //setup engine defined packet callbacks
            packetCallbacks = new Dictionary<byte, PacketReader>();
            RegisterDefinedCallbacks();

            isConnected = false;

            serverAddress = address;
            serverPort = port;

            receiveBS = new BitStream();
            writeBS = new BitStream();

            try
            {
                interfaceRak = RakPeer.GetInstance();
                interfaceRak.Startup(1, new SocketDescriptor(serverPort, serverAddress), 1);

                //use port + 1 for the server, and port for the client
                ushort serverPortP1 = (ushort)(serverPort + 1);
                serverAddressRak = new SystemAddress(serverAddress, serverPortP1);
                
                
                interfaceRak.Connect(serverAddress, serverPortP1, "", 0);

            }
            catch (Exception e)
            {
                System.Console.WriteLine(e.Message);
                isConnected = false;
            }
        }

        public bool IsConnected() { return isConnected; }

        private void RegisterDefinedCallbacks()
        {
            //DeltaUpdatePacket
            //RegisterPacketCallback(PacketType.P_IDREQ, DeltaUpdateP.readPacket);
            RegisterPacketCallback(PacketType.P_DELTAU, DeltaUpdateP.readPacket);
        }

        ~NetworkManager()
        {
            //cleanup
            RakPeer.DestroyInstance(interfaceRak);
        }
        public void WritePacket(IPacket packet)
        {
            if (isConnected)
            {
                writeBS.Reset();
                PacketReliability pr;
                PacketPriority pp = packet.writePacket(writeBS, out pr);
                interfaceRak.Send(writeBS, pp, pr,
                    (char)0,serverAddressRak,false);
            }
        }

        public override void Update(GameTime gameTime)
        {
            if (interfaceRak.GetConnectionState(new AddressOrGUID(serverAddressRak)) == ConnectionState.IS_CONNECTED)
            {
                packet = interfaceRak.Receive();
                if (packet != null)
                {
                    //process received packet

                    //predefined types
                    switch (packet.data[0])
                    {
                        case (byte)DefaultMessageIDTypes.ID_CONNECTION_REQUEST_ACCEPTED:
                            isConnected = true;
                            break;
                    }

                    //registered types
                    if (packet.data[0] > (byte)DefaultMessageIDTypes.ID_USER_PACKET_ENUM)
                    {
                        ReadPacket(packet, packet.data[0]);
                    }
                }

                interfaceRak.DeallocatePacket(packet);
                //Engine is single threaded so drawing should incur a large enough pause
                //System.Threading.Thread.Sleep(50);    
            }
        }

        public override void Draw(GameTime gameTime)
        {
            //No Need to draw anything in the network manager
        }

        public override void LoadContent()
        {
            //Nothing to load in the network manager
        }

        private void ReadPacket(Packet packet, byte type)
        {
            //receiveBS.Reset();
            //receiveBS.Write(packet.data, packet.length);
            //receiveBS.IgnoreBytes(1);
            byte[] b = new byte[packet.data.Length-2];
            Array.Copy(packet.data, 2, b, 0, packet.data.Length-2);
            if(packetCallbacks.ContainsKey(type)){
                PacketReader deleg = packetCallbacks[type];
                deleg(game,b);
            } else {
                Console.WriteLine("Packet received of non registered type " + type + ". Did we forget to register?");
            }
        }

        public void RegisterPacketCallback(PacketType type,PacketReader func)
        {
            if (!packetCallbacks.ContainsKey((byte)type))
            {
                packetCallbacks.Add((byte)type, func);
            }
            else
            {
                Console.WriteLine("Packet type " + type + " already registered.");
            }
        }
    }
}
